# Demo Controller
# Kevin Han May 2013
from ryu.base import app_manager

from ryu.ofproto import ether
from ryu.lib.packet import *

from ryu.lib.mac import haddr_to_str
from ryu.lib.ip import ipv4_to_str

from ryu.controller import ofp_event
from ryu.controller.handler import MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.controller import dpset
from ryu.controller import handler

from ryu.ofproto import ofproto_v1_0
from ryu.ofproto import ofproto_v1_0_parser
from ryu.ofproto import ofproto_parser

from twisted.python import log

import logging
import struct
from time import time
from socket import htons

from CookieGenerator import *

class demo_controller(app_manager.RyuApp):
    _CONTEXTS = {
        'dpset': dpset.DPSet
    }
    # Define logger.info display message prefix
    logger = logging.getLogger('ryu_Unicast')
    OFP_VERSIONS = [ofproto_v1_0.OFP_VERSION]
    
    def __init__(self, *args, **kwargs):
        super(demo_controller, self).__init__(*args, **kwargs)
        self.st = {} #switches dictionary with dpid with support for active handover between ports
        #st = {dpid : {srcmac : [[inport, time, active_flows]]}}
        self.bindpoints = {} #bicast AP bind points
        #bindpoints = {client : {dpid : [???]}} 
        self.flowdb = {} #flows for switch
        #flowdb = {dpid : {dst_addr : [[flow, buf, bufid, actions]]}}}
        self.t = time() #time offset for display
        self.ap = {} #for dpid of AP and their wlan port numbers
        #ap = {dpid : {port_no : port_name}}
        self.ts_ports = {} #stats for the tunneling server is kept separated
        #ts_ports = {port_name : port_no}   

        #########################################################################
        # HARDCODED NETWORK TOPOLOGY INFORMATION logger.info('JOINED NEW AP!')
        #########################################################################
        #Tunneling server OVS DPID
        self.tunnel_server_dpid = 1
        #Tunneling server OVS ports association with each AP {AP dpid : tunnel server port_name}
        self.tunnel_server_inport_ap = {2:'tap0',3:'tap1',4:'tap2'}
        #hardcoded handover order/sequence {AP dpid: sequence number}
        self.handover_sequence = {2:1,3:2,4:3}
        self.handover_sequence_rev = {1:2,2:3,3:4}
        #Change to dictionary later, initialize previousDpid to 2 (KH)
        self.previousDpid = 2
        self.verbosityLevel = 2
        self.FLOW_IDLE_TIMEOUT = 10 #idle time before flow is removed
        self.cg = CookieGenerator() #For generating cookie numbers
        
        # Flow dictionary
        self.DL_SRC     = "dl_src"
        self.DL_DST     = "dl_dst"
        self.DL_VLAN    = "dl_vlan"
        self.DL_VLAN_PCP = "dl_vlan_pcp"
        self.DL_TYPE    = "dl_type"
        self.NW_SRC     = "nw_src"
        self.NW_DST     = "nw_dst"
        self.NW_PROTO   = "nw_proto"
        self.NW_TOS     = "nw_tos"
        self.NW_SRC_N_WILD = "nw_src_n_wild"
        self.NW_DST_N_WILD = "nw_dst_n_wild"
        self.TP_SRC     = "tp_src"
        self.TP_DST     = "tp_dst"
        self.DATAPATH      = "datapath"
        self.INPORT    = "inport"
        self.DL_DST_RAW = "dl_dst_raw"
        self.DL_SRC_RAW = "dl_src_raw"
        
        # Ethernet Type (Dictionary for DL_TYPE)
        self.dl_type_dict = {}
        self.dl_type_dict[0x0800] = 'IP'
        self.dl_type_dict[0x0806] = 'ARP'
        self.dl_type_dict[0x8035] = 'RARP'
        self.dl_type_dict[0x8100] = 'VLAN'
        self.dl_type_dict[0x88cc] = 'LLDP'
        
        # IP Type (Dictionary for NW_PROTO)
        self.nw_proto_dict = {}
        self.nw_proto_dict[0]  = 'IP'
        self.nw_proto_dict[1]  = 'ICMP'
        self.nw_proto_dict[2]  = 'IGMP'
        self.nw_proto_dict[4]  = 'IPIP'
        self.nw_proto_dict[6]  = 'TCP'
        self.nw_proto_dict[9]  = 'IGRP'
        self.nw_proto_dict[17] = 'UDP'
        self.nw_proto_dict[47] = 'GRE'
        self.nw_proto_dict[89] = 'OSPF'
        self.nw_proto_dict[245] = 'Capsulator'
    
    def verbose(self, level, output):
        if(level <= self.verbosityLevel):
            self.logger.info(output)
    
    #Extract Flow method written by Kevin Han in May 2013 (Adapted from NOX)
    def extract_flow(self, msg):
        attrs = {}
        attrs[self.INPORT] = msg.in_port 
        attrs[self.DATAPATH] = msg.datapath
        attrs[self.NW_TOS] = 0
        #Create a packet
        newPacket = packet.Packet(msg.data)
        for i in newPacket:
            attrs[self.DL_SRC] = i.src
            attrs[self.DL_DST] = i.dst
            attrs[self.DL_SRC_RAW] = i.src
            attrs[self.DL_DST_RAW] = i.dst
            attrs[self.DL_TYPE] = i.ethertype
            try:
                p = newPacket.next()
            except StopIteration:
                return attrs
            if isinstance(p, vlan.vlan):
                attrs[self.DL_VLAN] = p.vid
                attrs[self.DL_VLAN_PCP] = p.pcp
                try:
                    p = newPacket.next()
                except StopIteration:
                    return attrs
            else:
                attrs[self.DL_VLAN] = 0xffff # XXX should be ryu.lib.packet.arp.arp object at 0x2135350>written OFP_VLAN_NONE
                attrs[self.DL_VLAN_PCP] = 0
            if isinstance(p, ipv4.ipv4):
                attrs[self.NW_SRC] = p.src
                attrs[self.NW_DST] = p.dst
                attrs[self.NW_PROTO] = p.proto
                try:
                    p = newPacket.next()
                except StopIteration:
                    return attrs
                if isinstance(p, udp.udp) or isinstance(p, tcp.tcp):
                    attrs[self.TP_SRC] = p.src_port
                    attrs[self.TP_DST] = p.dst_port
                else:
                    if isinstance(p, icmp.icmp):
                        attrs[self.TP_SRC] = p.type
                        attrs[self.TP_DST] = p.code
                    else:
                        attrs[self.TP_SRC] = 0
                        attrs[self.TP_DST] = 0
            elif isinstance(p, arp.arp):
                attrs[self.NW_SRC] = p.src_ip
                attrs[self.NW_DST] = p.dst_ip
                attrs[self.NW_PROTO] = p.proto
                try:
                    p = newPacket.next()
                except StopIteration:
                    return attrs
                if isinstance(p, udp.udp) or isinstance(p, tcp.tcp):
                    attrs[self.TP_SRC] = p.src_port
                    attrs[self.TP_DST] = p.dst_port
                else:
                    if isinstance(p, icmp.icmp):
                        attrs[self.TP_SRC] = p.type
                        attrs[self.TP_DST] = p.code
                    else:
                        attrs[self.TP_SRC] = 0
                        attrs[self.TP_DST] = 0
            else:
                attrs[self.NW_SRC] = 0
                attrs[self.NW_DST] = 0
                attrs[self.NW_PROTO] = 0
                attrs[self.TP_SRC] = 0
                attrs[self.TP_DST] = 0
            break;
        return attrs
    
    def print_flowdb(self):
        print '\ndpid\t\tdl_type\t\tnw_proto\tnw_src\t\tnw_dst\t\tdl_src\t\t\tdl_dst'
        print '----\t\t-------\t\t--------\t------\t\t------\t\t------\t\t\t------'
        for dpid_key in self.flowdb.iterkeys():
            for dst_key in self.flowdb[dpid_key].iterkeys():
                #verbose(1, 'dpid: '+str(dpid_key)+'\tdst_addr: '+str(dst_key))
                #NOW WE HAVE A LIST, for each tuple element in the list, access tuple element 0 and check for the parameters, if present, print the parameters
                for index in range(len(self.flowdb[dpid_key][dst_key])):
                    if isinstance(self.flowdb[dpid_key][dst_key][index], list):
                        toPrint = hex(dpid_key).rstrip("L").lstrip("0x").ljust(14)+'\t'
                        if 'dl_type' in self.flowdb[dpid_key][dst_key][index][0]:
                            #verbose(1, 'dl_type: '+str(self.flowdb[dpid_key][dst_key][index][0]['dl_type']))
                            toPrint += str(self.flowdb[dpid_key][dst_key][index][0]['dl_type'])+'\t\t'
                        else:
                            toPrint += 'N/A'+'\t'
                        if 'nw_proto' in self.flowdb[dpid_key][dst_key][index][0]:
                            #verbose(1, 'nw_proto: '+str(self.flowdb[dpid_key][dst_key][index][0]['nw_proto']))
                            toPrint += str(self.flowdb[dpid_key][dst_key][index][0]['nw_proto'])+'\t\t'
                        else:
                            toPrint += 'N/A'+'\t'
                        if 'nw_dst' in self.flowdb[dpid_key][dst_key][index][0] and 'nw_src' in self.flowdb[dpid_key][dst_key][index][0]:
                            #verbose(1, 'nw_src: '+str(self.flowdb[dpid_key][dst_key][index][0]['nw_src'])+'\tnw_dst: '+str(self.flowdb[dpid_key][dst_key][index][0]['nw_dst']))
                            toPrint += ipv4_to_str(self.flowdb[dpid_key][dst_key][index][0]['nw_src']).ljust(13)+'\t'+ipv4_to_str(self.flowdb[dpid_key][dst_key][index][0]['nw_dst']).ljust(13)+'\t'
                        else:
                            toPrint += 'N/A'+'\t'+'N/A'+'\t'
                        if 'dl_src' in self.flowdb[dpid_key][dst_key][index][0] and 'dl_dst' in self.flowdb[dpid_key][dst_key][index][0]:
                            #verbose(1, 'dl_src: '+str(self.flowdb[dpid_key][dst_key][index][0]['dl_src'])+'\tdl_dst: '+str(self.flowdb[dpid_key][dst_key][index][0]['dl_dst']))
                            toPrint += (self.flowdb[dpid_key][dst_key][index][0]['dl_src'])+'\t'+(self.flowdb[dpid_key][dst_key][index][0]['dl_dst'])
                        else:
                            toPrint += 'N/A'+'\t'+'N/A'+'\t'
                        print toPrint
        return
    
    def do_l2_learning(self, msg):
        pkt = packet.Packet(msg.data)
        datapath = msg.datapath
        dpid = datapath.id
        inport = msg.in_port
        srcaddr = haddr_to_str(pkt.protocols[0].src)
        if ord(srcaddr[0]) & 1: #Broadcast, do not learn
            return
        if self.st[dpid].has_key(srcaddr): #if this mac is already registered on this switch
            inport_found = False
            ports = []
            for index, entry in enumerate(self.st[dpid][srcaddr]): #for each inport element
                if entry[0] == inport: #already know this port, simply update time      
                    active_flows = self.st[dpid][srcaddr][index][2]          
                    self.st[dpid][srcaddr][index]=[inport, time(), active_flows]
                    inport_found = True
                    break
                ports.append(entry[0])
            if not inport_found: #check to see if this is a mobile client because we need to keep duplicate
                if not self.bindpoints.has_key(srcaddr): #src was moved to another port
                    self. verbose(2, '%.6f: MAC has moved from '%(time()-self.t)+str(entry[0])+'to'+str(inport))      
                    self.st[dpid][srcaddr] = [[inport, time(), 0]]
                    self.reinstall_flow_actions(dpid,srcaddr)
        else: #no src_mac entry found
            self.verbose(2, '%.6f: learned MAC '%(time()-self.t)+haddr_to_str(pkt.protocols[0].src)+' on dpid %x : inport %d' % (dpid,inport))
            self.st[dpid][srcaddr]=[[inport, time(), 0]] #create new entry
    
    def reinstall_flow_actions(self, dpid, dstaddr):
        if not dpid in self.flowdb or not dstaddr in self.flowdb[dpid]: 
            return False #if no such dst_addr in flowdb
        self.verbose(2, '%.6f: Reinstalling all flows destination: '%(time()-self.t)+ dstaddr + ' on DPID %x' + str(dpid))
        ports = self.get_learned_ports(dpid,dstaddr) #get all learned ports
        self.reinstall_switch_ports(dpid,dstaddr,ports)
        return True
    
    def reinstall_switch_ports(self,dpid,dstaddr,ports):
        # guard against misuse on empty database
        if not self.flowdb.has_key(dpid) or not self.flowdb[dpid].has_key(dstaddr) or not self.flowdb[dpid][dstaddr]:
            self.verbose(2, '%.6f: Switch %x has no flows to reinstall in database' %(time()-self.t,dpid))
            return False
        for entry in self.flowdb[dpid][dstaddr]:
            (flow,buf,bufid) = entry[0:3] #extract flow information
            inport = flow[self.INPORT]
            if not ports: #safeguard in case dstaddr empty -> so ports empty
                # Delete the flow since no longer have port, include wildcard
                self.delete_datapath_flow(dpid,flow) #TODO:IMPLEMENT WITH FLOW MOD IF NECESSARY (NOT SURE IF CODE COMES IN HERE)
                # Note: this triggers a flow removed event, so no need to update flowdb yet
            else: #not empty
                actions = self.get_list_actions(ports,inport) 
                self.install_flow_forward_ports(dpid,flow,buf,bufid,actions)
        return
    
    def forward_l2_packet(self, msg):
        pkt = packet.Packet(msg.data)
        dpid = msg.datapath.id
        inport = msg.in_port
        buf = msg.buf
        bufid = msg.buffer_id
        dstaddr = haddr_to_str(pkt.protocols[0].dst)
        if (not ord(dstaddr[0]) & 1) and self.st[dpid].has_key(dstaddr):
            flow = self.extract_flow(msg) #from util.py
            flow[self.INPORT] = inport #set the inport parameter of this flow
            flow[self.DL_DST] = haddr_to_str(flow[self.DL_DST])
            flow[self.DL_SRC] = haddr_to_str(flow[self.DL_SRC])
            # exact match for now (no wildcard)
            flow[self.NW_SRC_N_WILD] = 0L
            flow[self.NW_DST_N_WILD] = 0L
            #get all learned ports
            ports = self.get_learned_ports(dpid,dstaddr)
            if ports: #safeguard in case dstaddr e source, dst etc.)
                self.print_flowdb()
                actions = self.get_list_actions(ports,inport)
                self.install_flow_forward_ports(dpid,flow,buf,bufid,actions)
            else: #if empty
                # haven't learned destination
                #MAC or broadcast. Flood
                out_port = ofproto_v1_0.OFPP_FLOOD
                actions = [msg.datapath.ofproto_parser.OFPActionOutput(out_port)]
                out = msg.datapath.ofproto_parser.OFPPacketOut(datapath=msg.datapath, buffer_id=bufid, in_port=inport, actions=actions)
                msg.datapath.send_msg(out)
        else:
            # haven't learned destination MAC or broadcast. Flood
            out_port = ofproto_v1_0.OFPP_FLOOD
            actions = [msg.datapath.ofproto_parser.OFPActionOutput(out_port)]
            out = msg.datapath.ofproto_parser.OFPPacketOut(datapath=msg.datapath, buffer_id=bufid, in_port=inport, actions=actions)
            msg.datapath.send_msg(out)
    
    #Get list of inports for a MAC address and dpid
    def get_learned_ports(self, dpid, dstaddr):
        ports = []
        #safeguard in case dstaddr empty
        if not self.st[dpid].has_key(dstaddr) or not self.st[dpid][dstaddr]: 
            return ports
        else :
            for prt in self.st[dpid][dstaddr]: #for all learned inport for this MAC        
                ports.append(prt[0])
            return ports   
    
    #Get list of actions for a list of ports and inport
    def get_list_actions(self, ports, inport = None):
        actions = [] #start as empty
        for port in ports: #for all learned inport for this MAC given as input    
            if inport is not None and port == inport:
                self.verbose(2, '%.6f: **warning** learned port is inport '%(time()-self.t)+str(inport))        
                # do nothing so action list is empty
            else:
                # We know the outport, set up the port in action list
                actions.append(ofproto_v1_0_parser.OFPActionOutput(port))
        return actions #can be empty if just inport 
    
    def install_flow_forward_ports(self,dpid,flow,buf,bufid,actions):
        if actions: #not empty
            action_str = ''
            for entry in actions:
                action_str += ' ' + str(entry.port)
            self.verbose(2, '%.6f: Flow INSTALL for '%(time()-self.t) + self.flow_to_str(flow) +' forward to port' + action_str + ' bufID ' + str(bufid) +' on DPID %x'%(dpid))
        else: #empty
            self.verbose(2 ,'%.6f: Flow BLOCKED for '%(time()-self.t) + self.flow_to_str(flow) + ' on DPID %x'%(dpid))
        # install flow command
        #MAY NEED MORE MATCHING WILDCARDS
        wild = ofproto_v1_0.OFPFW_ALL
        wild &= ~ofproto_v1_0.OFPFW_IN_PORT
        wild &= ~ofproto_v1_0.OFPFW_DL_DST
        wild &= ~ofproto_v1_0.OFPFW_DL_TYPE
        #wild &= ~ofproto_v1_0.OFPFW_NW_PROTO
        #Check if sending correct primitives
        print flow[self.DL_TYPE]
        match = ofproto_v1_0_parser.OFPMatch(wild, flow[self.INPORT], 0, flow[self.DL_DST_RAW], 0, 0, flow[self.DL_TYPE], 0, 0, 0, 0, 0, 0)
        #check flags, may need to set OFPFF_CHECK_OVERLAP
        datapath = flow[self.DATAPATH]
        tempcookie = self.cg.get_cookie()
        req = ofproto_v1_0_parser.OFPFlowMod(datapath=datapath, match=match, cookie=tempcookie, command=ofproto_v1_0.OFPFC_ADD, 
                                             idle_timeout=self.FLOW_IDLE_TIMEOUT, hard_timeout=ofproto_v1_0.OFP_FLOW_PERMANENT,
                                             priority=ofproto_v1_0.OFP_DEFAULT_PRIORITY, flags=ofproto_v1_0.OFPFF_SEND_FLOW_REM,
                                             actions=actions)
        datapath.send_msg(req)
        self.insert_flowdb_entry(dpid,flow,buf,bufid,actions,tempcookie) #insert into flowdb
        
    def insert_flowdb_entry(self, dpid, flow, buf, bufid, actions, cookie): 
        #Note: the flows sent from flow_removed are in long
        #whereas the flows extracted from packets are in array
        #thus, must decide a common storage type in database, which is in here   
        dst = flow[self.DL_DST]
        src = flow[self.DL_SRC]
        if not self.flowdb.has_key(dpid):
            self.flowdb[dpid] = {}
        if not self.flowdb[dpid].has_key(dst):
            self.flowdb[dpid][dst] = []
        flow_exist = False
        for index in range(len(self.flowdb[dpid][dst])): #for each entry
            # update even if 'flow' is just a subset of 'entry[0]' (higher precedence due to wildcard)        
            # if all(item in inst.flowdb[dpid][dst][index][0].items() for item in flow.items()):
            if self.flowdb[dpid][dst][index][0] == flow:
                if not flow_exist:
                    self.flowdb[dpid][dst][index] = [flow,buf,bufid,actions,cookie] #KH
                    flow_exist = True
                    #no break point just to aggregate existing flows if necessary
                else: #when a second existing flow also match, need to delete the entry
                    self.flowdb[dpid][dst].pop(index)
                    self.decrement_active_flows(dpid,src,flow[self.INPORT])
                    # TODO: need to delete flow, does wildcard overwrite old ones? or are the old ones "zombie" entries
        if not flow_exist:
            self.flowdb[dpid][dst].append([flow,buf,bufid,actions,cookie,1])
            self.increment_active_flows(dpid,src,flow[self.INPORT])
    
    def decrement_active_flows(self, dpid, src_mac, inport):
        for port in self.st[dpid][src_mac]:
            if port[0] == inport:
                port[2] = port[2] - 1
                if port[2] == 0:
                    port[1] = time()
                return
    
    def increment_active_flows(self, dpid, src_mac, inport):
        for port in self.st[dpid][src_mac]:
            if port[0] == inport:
                port[2] = port[2] + 1
                return
    
    # --
    # To convert relevant info from flow to string for printing
    # --
    def flow_to_str(self, flow):
        string = ''
        if (flow.has_key(self.DL_TYPE)):
            string += string + 'DL_TYPE: '+self.dl_type_dict[flow[self.DL_TYPE]] + '; '
            if (flow.has_key(self.DL_SRC) and flow.has_key(self.DL_DST)):
                string += 'MAC: ' + (flow[self.DL_SRC]) + '->' + (flow[self.DL_DST]) + '; '
            if (flow[self.DL_TYPE] == 0x0800): #IP
                if flow.has_key(self.NW_PROTO):
                    string += 'NW_PROTO: '+self.nw_proto_dict[flow[self.NW_PROTO]]+'; '
                if (flow.has_key(self.NW_SRC) and flow.has_key(self.NW_DST)):
                    string += 'IP: ' + ipv4_to_str(flow[self.NW_SRC]) + '->' + ipv4_to_str(flow[self.NW_DST]) + '; '
                if (flow.has_key(self.TP_SRC) and flow.has_key(self.TP_DST)):
                    string += 'Port '+str(flow[self.TP_SRC]) + '-> Port '+str(flow[self.TP_DST]) + '; '
            string += 'Inport: '+str(flow[self.INPORT])+' '
        else:
            string = 'Unrecognized flow: ' + str(flow)
        return string
        
    def detect_mobile_client(self,msg):
        print 'Possible Mobile Client Detected: TO IMPLEMENT!'
    
    # --
    # Handler for Datapath Leave/Join Events
    # --
    # NEW IN RYU: Port is referenced by the following indexes
    # 0: port_no, 1: hw_addr, 2: name, 3: config, 4: state, 5: curr, 6: advertised, 7: supportedself.ap[dpid] = {} #initialize
    @handler.set_ev_cls(dpset.EventDP)
    def datapath_callback(self, ev):
        datapath = ev.dp
        dpid = datapath.id
        if ev.enter: #Datapath Join
            ports = ev.ports
            self.verbose(2, '%.6f: Switch %x has joined the network'%((time()-self.t),dpid))
            #register dpid if new switch device
            if not self.st.has_key(dpid):
                self.st[dpid] = {}
            #check if tunneling server
            if dpid == self.tunnel_server_dpid:
                self.verbose(2, '%.6f: Tunneling server switch joined at DPID '%(time()-self.t) + "%x"%(dpid))
                for port in ports: #store port number for switch
                    tempstr = port[2].partition('\x00')
                    self.ts_ports[tempstr[0]] = port[0]
                print self.ts_ports
            else: #check if it is a wireless AP
                ap_found = False #flag to check ap
                for port in ports:
                    if ('wlan' in port[2]) or ('tap' in port[2]): #if wlan port name detected
                        if not ap_found: #first time
                            self.ap[dpid] = {} #initialize
                            ap_found = True
                        tempstr = port[2].partition('\x00')    
                        self.ap[dpid][port[0]] = tempstr[0]
                if ap_found: #notification message about AP
                    self.verbose(2, '%.6f: Wireless Access Point Detected for DPID '%(time()-self.t)+ "%x"%(dpid))
                    print self.ap
        else: #Datapath Leave
            self.verbose(2, '%.6f: Switch DPID %x has left the network'%((time()-self.t),dpid))
            # perform database cleanup    
            if self.st.has_key(dpid):
                del self.st[dpid]
            if self.flowdb.has_key(dpid):
                del self.flowdb[dpid]
            if self.ap.has_key(dpid):
                del self.ap[dpid]
            for client in self.bindpoints.keys():
                if self.bindpoints[client].has_key(dpid):
                    del self.bindpoints[client][dpid]
                    self.verbose(2, '%.6f: AP '%(time()-self.t)+ str(dpid) + ' disconnected from client '+ haddr_to_str(client))
                # if empty
                if not self.bindpoints[client]:
                    del self.bindpoints[client]
                    self.verbose(2, '%.6f: Removed client '%(time()-self.t)+haddr_to_str(client)+' not associated with any AP')
            if dpid == self.tunnel_server_dpid: #tunneling server disconnected?
                self.verbose(2, '%.6f: Tunneling server disconnected!'%(time()-self.t))
                self.ts_ports = {} #reset to empty
        return
    
    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def packet_in_callback(self, ev):
        pkt = packet.Packet(ev.msg.data)
        if pkt.broken_packet:
            return #Ignore LLC packets
        datapath = ev.msg.datapath
        dpid = datapath.id
        inport = ev.msg.in_port
        
        if pkt.parsed_bytes == 0:
            log.msg('Ignoring incomplete packet',system='unicast')
            
        #Drop LLDP Packets    
        for i in pkt.protocols:
            if isinstance(i, ethernet.ethernet):
                if i.ethertype == ether.ETH_TYPE_LLDP:
                    return
        
        # learn MAC on incoming port
        self.do_l2_learning(ev.msg)
        
        # forward the flow by installing it in flow table
        self.forward_l2_packet(ev.msg)
        
        # mobility n-casting 
        if self.ap.has_key(dpid):
            if self.ap[dpid].has_key(inport):
                self.detect_mobile_client(ev.msg)
                
        return 
    
    @set_ev_cls(ofp_event.EventOFPFlowRemoved, MAIN_DISPATCHER)
    def flow_removed_callback(self, ev):
        dpid = ev.msg.datapath.id
        cno = ev.msg.cookie
        for i in self.flowdb[dpid]:
            for j in self.flowdb[dpid][i]:
                if j[4] == cno: #if cookies match
                    flow = j[0]
                    #print flow
        if not self.remove_flowdb_entry(dpid, flow):
            self.verbose(2, '%.6f: Error removing flowdb entry: flow mismatch!' %(time()-self.t)) 
            self.print_flowdb();
        else:
            self.verbose(2, '%.6f: Flow REMOVED '%(time()-self.t) + ' on DPID %x'%dpid)
        return
    
    def remove_flowdb_entry(self, dpid, flow):
        success = False
        src = flow[self.DL_SRC]
        dst = flow[self.DL_DST]      
        if not self.flowdb.has_key(dpid):
            return success
        if not self.flowdb[dpid].has_key(dst):
            return success
        for entry in self.flowdb[dpid][dst]:
        # remove even if 'flow' is just a subset of 'entry[0]' (higher precedence)
            # if all(item in entry[0].items() f# --
            if entry[0] == flow:
                inport = entry[0][self.INPORT]
                self.flowdb[dpid][dst].remove(entry)
                self.decrement_active_flows(dpid,src,inport)
                success = True
                # no break point because wildcard flows can cause multiple sub-flow removal           
        #remove empty structures
        if not self.flowdb[dpid][dst]:
            del self.flowdb[dpid][dst]
        return success